/*
 * micro_libxml.c
 *
 *  Created on: Feb 27, 2012
 *      Author: Administrator
 */
#include <string.h>
#include "micro_libxml.h"
#include "utils.h"
static micro_libxml_xmpp_element_t *micro_libxml_element_holder;
static micro_libxml_attribute_t *micro_libxml_attribute_holder;
static pthread_mutex_t micro_libxml_holder_mutex;

static int micro_libxml_skipbegingsnrt(char **c,int len);
static micro_libxml_xmpp_element_t *micro_libxml_get_available_element();
static void micro_libxml_free_xmpp_element(micro_libxml_xmpp_element_t *element);


int micro_libxml_init()
{
	micro_libxml_element_holder = NULL;
	micro_libxml_attribute_holder = NULL;
	pthread_mutex_init(&micro_libxml_holder_mutex,NULL);
	return S_OK;
}
int micro_libxml_destroy()
{

	micro_libxml_xmpp_element_t *tmp_element;
	micro_libxml_attribute_t *tmp_attribute;

	pthread_mutex_lock(&micro_libxml_holder_mutex);
	while(micro_libxml_element_holder)
	{
		tmp_element = micro_libxml_element_holder->next_in_memory;
		chkndfree(micro_libxml_element_holder);
		micro_libxml_element_holder = tmp_element;
	}
	while(micro_libxml_attribute_holder)
	{
		tmp_attribute = micro_libxml_attribute_holder->next_in_memory;
		chkndfree(micro_libxml_attribute_holder);
		micro_libxml_attribute_holder = tmp_attribute;
	}
	pthread_mutex_unlock(&micro_libxml_holder_mutex);
	pthread_mutex_destroy(&micro_libxml_holder_mutex);

	return S_OK;
}
static int micro_libxml_skipbegingsnrt(char **c,int len)//remove begining \r\n\s\t
{
	int skiped = 0;
	while((**c == ' ' || **c == '\r'|| **c == '\n' || **c == '\t') && skiped <= len)
	{
		(*c)++;
		++skiped;
	}
	return skiped;
}
static micro_libxml_xmpp_element_t *micro_libxml_get_available_element()
{
	micro_libxml_xmpp_element_t *result;
	pthread_mutex_lock(&micro_libxml_holder_mutex);
	if(!micro_libxml_element_holder)
	{
		micro_libxml_element_holder = sipxmpp_calloc(sizeof(micro_libxml_xmpp_element_t));
	}
	result = micro_libxml_element_holder;
	while(result)
	{
		if(!result->occupied)
		{
			break;
		}
		else if(!result->next_in_memory)
		{
			result->next_in_memory = sipxmpp_calloc(sizeof(micro_libxml_xmpp_element_t));
		}
		result = result->next_in_memory;
	}
	memset(result,0,sizeof(micro_libxml_xmpp_element_t) - sizeof(micro_libxml_xmpp_element_t *));//we dont cleanup next_in_memory;
	result->occupied = 1;
	pthread_mutex_unlock(&micro_libxml_holder_mutex);
	return result;
}
static micro_libxml_attribute_t *lightxmpplib_get_available_attribute()
{
	micro_libxml_attribute_t *result;

	pthread_mutex_lock(&micro_libxml_holder_mutex);
	if(!micro_libxml_attribute_holder)
		micro_libxml_attribute_holder = sipxmpp_calloc(sizeof(micro_libxml_attribute_t));
	result = micro_libxml_attribute_holder;
	while(result)
	{
		if(!result->occupied)
		{
			break;
		}
		else if(!result->next_in_memory)
		{
			result->next_in_memory = sipxmpp_calloc(sizeof(micro_libxml_attribute_t));
		}
		result = result->next_in_memory;
	}
	memset(result ,0,sizeof(micro_libxml_attribute_t) - sizeof(micro_libxml_attribute_t*));
	result->occupied = 1;
	pthread_mutex_unlock(&micro_libxml_holder_mutex);
	return result;
}
static void micro_libxml_free_xmpp_element(micro_libxml_xmpp_element_t *element)
{
	micro_libxml_attribute_t *tmp;
	if(element->childs)
	{
		micro_libxml_free_xmpp_element(element->childs);
	}
	if(element->next)
	{
		micro_libxml_free_xmpp_element(element->next);
	}
	pthread_mutex_lock(&micro_libxml_holder_mutex);
	while(element->attributes)
	{
		tmp = element->attributes->next;
		memset(element->attributes,0,sizeof(micro_libxml_attribute_t) - sizeof(micro_libxml_attribute_t *));
		element->attributes = tmp;
	}
	memset(element,0,sizeof(micro_libxml_xmpp_element_t) - sizeof(micro_libxml_xmpp_element_t *));

	pthread_mutex_unlock(&micro_libxml_holder_mutex);
}

micro_libxml_attribute_t *micro_libxml_findattribute(micro_libxml_xmpp_element_t *element,char *attributename)
{
	micro_libxml_attribute_t *tmp = element->attributes;
	int attributenamelen;
	while(tmp)
	{
		attributenamelen = strlen(attributename);
		if(!memcmp(tmp->attribute_name,attributename,MIN(tmp->attribute_name_len,attributenamelen)))
		{
			return tmp;
		}
		tmp = tmp->next;
	}
	return NULL;
}

micro_libxml_xmpp_element_t *micro_libxml_findelement(micro_libxml_xmpp_element_t *element,char *xpath)
{
	char *tmp = xpath;
	micro_libxml_xmpp_element_t *tmpelement = element;
	while(*tmp == '/')
	{
		tmp++;
	}
	Log("locating=%s",tmp);
	if(!strchr(tmp,'/'))
	{
		while(tmpelement)
		{
			if(!memcmp(tmpelement->element_name,tmp,tmpelement->element_name_len))
			{
				return tmpelement;
			}
			tmpelement = tmpelement->next;
		}
		return NULL;
	}

	while(tmpelement)
	{
		if(!memcmp(tmpelement->element_name,tmp,tmpelement->element_name_len))
		{
			return micro_libxml_findelement(tmpelement->childs,strchr(tmp,'/') + 1);
		}
		tmpelement = tmpelement->next;
	}
	return NULL;
}

static void print_attribute_tree(int count,micro_libxml_xmpp_element_t *element)
{
	if(element)
	{
		Log("%.*s[%.*s][%.*s][%.*s]",count,"\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",element->element_ns_len,element->element_ns,element->element_name_len,element->element_name,element->element_value_len,element->element_value);
		micro_libxml_attribute_t *attribute = element->attributes;
		while(attribute)
		{
			Log("attribute:%.*s[%.*s]=[%.*s]",count,"\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",attribute->attribute_name_len,attribute->attribute_name,attribute->attribute_value_len,attribute->attribute_value);
			attribute = attribute->next;

		}
		if(element->childs)
		{
			print_attribute_tree(count+1,element->childs);
		}
		if(element->next)
		{
			print_attribute_tree(count,element->next);
		}
	}
}
#define e_end (element_endslash ? element_endslash:element_end)
int micro_libxml_parse_stream(micro_libxml_parser_t *parser,void *userdata)
{
	int read_len = 0;
	micro_libxml_xmpp_element_t *root_element = NULL;
	//xmpp_connection_t *connection = (xmpp_connection_t*)userdata;

	char *element_start,*element_beginslash,*element_endslash, *element_end, *current,*colon;
	int i;
	micro_libxml_attribute_t *current_attribute = NULL;
	micro_libxml_attribute_t *tmp_attribute;
	micro_libxml_xmpp_element_t *current_element = NULL;

	parser->readbuffer_current = parser->readbuffer;
	while(!*(parser->stopping))
	{
		if(parser->readbuffer + parser->readbuffer_available_len <= parser->readbuffer_current)
		{
			read_len = parser->read(parser,userdata);
			if(read_len <= 0)
				break;
			continue;
		}
		element_start = memchr(parser->readbuffer_current,'<',parser->readbuffer_available_len - (parser->readbuffer_current - parser->readbuffer));
		if(!element_start || element_start == parser->readbuffer + parser->readbuffer_available_len)
		{
			read_len = parser->read(parser,userdata);
			if(read_len <= 0)
				break;
			continue;
		}
		element_end = memchr(element_start,'>',parser->readbuffer_available_len - (element_start - parser->readbuffer));
		if(!element_end)
		{
			read_len = parser->read(parser,userdata);
			if(read_len <= 0)
				break;
			continue;
		}
		element_beginslash = NULL;
		for(i = 1 ; i < element_end - element_start;i++)
		{
			if(element_start[i] == '/')
			{
				element_beginslash = element_start + i;
				break;
			}
			else if(element_start[i] != ' ')
			{
				break;
			}
		}
		element_endslash = NULL;
		for(i = 1 ; i < element_end - element_start;i++)
		{
			if(element_end[-i] == '/')
			{
				element_endslash = element_end - i;
				break;
			}
			else if(element_end[-i] != ' ')
			{
				break;
			}
		}
		if(element_beginslash && element_endslash)
		{
			LogE("cann't have begin slash and end slash together.");
			return S_ERROR;
		}
		else if(element_beginslash && !current_element)
		{
			LogE("can not start with end slash if there is no current_element.");
			return S_ERROR;
		}
		else if(element_beginslash)
		{
			current = element_beginslash + 1;
			micro_libxml_skipbegingsnrt(&current,element_end - element_beginslash);
			if(current_element->element_name && !xmpp_cmpstr(current_element->element_name,current + (current_element->element_ns_len > 0 ? current_element->element_ns_len + 1 : 0),current_element->element_name_len)
					&&(current_element->element_ns_len ==0 || !xmpp_cmpstr(current_element->element_ns,current,current_element->element_ns_len)))
			{

				current_element->element_value_len = element_start - current_element->element_value;
				current_element->element_end = element_end;
				parser->readbuffer_current = element_end + 1;

				goto element_end_breaker;
			}
			else
			{
				Log("wrong end element,to compare=%.*s",current_element->element_name_len,current_element->element_name);
				Log("current is:%.*s",(unsigned int)(element_end - current),current)
				return S_ERROR;
			}
		}
		else
		{

			micro_libxml_xmpp_element_t *created_element = micro_libxml_get_available_element();

			if(current_element)
			{
				created_element->parent = current_element;
				if(!current_element->childs)
				{
					current_element->childs = created_element;
				}
				else
				{
					micro_libxml_xmpp_element_t *tmp_element = current_element->childs;
					while(tmp_element->next)
						tmp_element = tmp_element->next;
					tmp_element->next = created_element;
				}
			}
			else if(!root_element)
			{
				root_element = created_element;
			}
			else
			{
				micro_libxml_xmpp_element_t *tmp_element = root_element;
				while(tmp_element->next)
					tmp_element = tmp_element->next;
				tmp_element->next = created_element;
			}
			current_element = created_element;
			current_element->element_start = element_start;
			current = element_start + 1;

			micro_libxml_skipbegingsnrt(&current,e_end - element_start);
			current_element->element_name = current;

			current = memchr(current,' ',element_end - current);
			if(current)
			{
				current_element->element_name_len = current - current_element->element_name;
				current++;
			}
			else
			{
				current_element->element_name_len = e_end - current_element->element_name;
			}
			colon = memchr(current_element->element_name,':',current_element->element_name_len);
			if(colon)
			{
				current_element->element_ns = current_element->element_name;
				current_element->element_ns_len = colon - current_element->element_name;
				current_element->element_name = colon + 1;
				current_element->element_name_len = current_element->element_name_len - current_element->element_ns_len - 1;
			}
			if(current)
			{
				micro_libxml_skipbegingsnrt(&current,e_end - current);
			}
			while(current && current < e_end) //attributes
			{
				current_attribute = lightxmpplib_get_available_attribute();
				if(!current_element->attributes)
					current_element->attributes = current_attribute;
				else
				{
					tmp_attribute = current_element->attributes;
					while(tmp_attribute->next)
						tmp_attribute = tmp_attribute->next;
					tmp_attribute->next = current_attribute;
				}
				current_attribute->attribute_name = current;
				current = memchr(current,'=',e_end - current - 1); // this -1 means we expect there is at least one byte after '='. or it's a bad attribute.
				if(current)
				{
					current_attribute->attribute_name_len = current - current_attribute->attribute_name;
					current_attribute->attribute_value = ++current;
					if(current[0] == '\'' || current[0] == '\"')
					{
						if(e_end - current >= 1 && (current = memchr(current + 1,current[0],e_end - current - 1)) != NULL) //we need it's paired quota
						{
							current_attribute->attribute_value++;
							current_attribute->attribute_value_len = current - current_attribute->attribute_value;
							current++;
						}
						else
						{
							LogE("no end quota.");
							return S_ERROR;
						}
					}
					else//attribute value is not quotaed.
					{
						current = memchr(current,' ',e_end - current);
						if(current)
						{
							current_attribute->attribute_value_len = current - current_attribute->attribute_value;
							current++;
						}
						else
						{
							//element end.
							current_attribute->attribute_value_len = e_end - current_attribute->attribute_value;
							break;
						}
					}
				}
				else
				{
					LogE("no '=' after attribute start");
					return S_ERROR;
				}
				micro_libxml_skipbegingsnrt(&current,e_end - current);
			}

			if(element_endslash)
			{
				current_element->element_value = element_end + 1;//just let it not be NULL.
				current_element->element_value_len = 0;
				current_element->element_end = element_end;
			}
			else
			{
				current_element->element_value = element_end + 1;
			}
			parser->readbuffer_current = element_end + 1;
			if(element_endslash)
				goto element_end_breaker;
		}
		continue;
		element_end_breaker:
		{
			enum MICRO_LIBXML_FOUND_ELEMENT_RETURN cb_result = parser->found(parser,userdata,current_element);
			if(cb_result == MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR)
			{
				LogE("found_element_callback error.");
				if(root_element)
				{
					micro_libxml_free_xmpp_element(root_element);
					root_element = NULL;
				}
				break;
			}
			else if(cb_result == MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_NOTHING)
			{
				current_element = current_element->parent;
			}
			else if (cb_result == MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_ROOT)
			{
				parser->readbuffer_current = parser->readbuffer;
				parser->readbuffer_available_len = 0;
				if(root_element)
				{
					micro_libxml_free_xmpp_element(root_element);
					root_element = NULL;
				}
				current_element = NULL;
			}
			else if(cb_result == MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_CURRENT_ELEMENT)
			{
				micro_libxml_xmpp_element_t *tmp_element;
				int data_move_count;
				data_move_count = parser->readbuffer_available_len - (current_element->element_end - parser->readbuffer);
				parser->readbuffer_available_len -= (current_element->element_end - current_element->element_start + 1);

				//move buffered data from current_element->element_end + 1 to current_element->start.
				parser->readbuffer_current = current_element->element_start;
				for(i = 0 ; i < data_move_count ; i++)
				{
					*(parser->readbuffer_current + i) = *(current_element->element_end + 1 + i);
				}
				LogD("currentxml=%.*s",(int)parser->readbuffer_available_len,parser->readbuffer);
				tmp_element = current_element->parent;
				//release current_element
				micro_libxml_free_xmpp_element(current_element);
				LogD("freeed current_element");
				if(!tmp_element && current_element == root_element) //root
				{
					root_element = NULL;
					current_element = NULL;
					tmp_element = NULL;
				}
				else if(tmp_element && current_element == tmp_element->childs) //first child.
				{
					tmp_element->childs = NULL;
					current_element = tmp_element;
					tmp_element = NULL;
				}
				else if(!tmp_element)  //next of root.
				{
					tmp_element = root_element;
				}
				else //second or more level. not first child.
				{
					tmp_element = tmp_element->childs;
				}

				if(tmp_element)
				{
					while(tmp_element->next != NULL)
					{
						if(tmp_element->next == current_element)
						{
							tmp_element->next = NULL; //current_element must be the last node.
							current_element = tmp_element;
							break;
						}
						tmp_element = tmp_element->next;
					}
				} //  !tmp_element means current_element is first child. we have already set it's parent's childs NULL. so we do nothing here.

				continue;
			}
			else
			{
				Log("not supported freeed value.");
				break;
			}
		}
	}
	if(root_element)
	{
		micro_libxml_free_xmpp_element(root_element);
	}
	return 0;
}
